#include "game_flow_manager.h"
#include "game_state.h"
//#include "metrics.h"
#include "ui_state.h"
#include "ui_popup.h"

#include "console/console.h"

#include "font/font_manager.h"

#include "script/scripthelper.h"
#include "application/game_application.h"

#include "util/timer.h"				// log
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    
#include "util/profiler.h"		    

#ifdef _ENABLE_LUABIND
#include <luabind/luabind.hpp>
#include <luabind/copy_policy.hpp>
#endif // _ENABLE_LUABIND
#include <cassert>

using namespace gameflow;


GameFlowManager&	GameFlowManager::Instance()
{
	static GameFlowManager	Inst;
	return Inst;
}

GameFlowManager::GameFlowManager() : 
	num::NamedSet<GameState*>(),
	m_Transition(),
	m_pCurrentState(NULL),
	m_pPreviousState(NULL),
	m_pTransitionState(NULL),
	m_TransitionTime(0.0f),
	m_TimePerFrame(1.0f / 60.0f),
	m_TimeSinceLastFrame(0.0f),
	m_pCamera(NULL),
	m_LastGainFocus(0.0f),
	m_pCustomTickCB(0),
	m_Render_dt(0),
    m_IsPause(false)
{
//	Metrics::Instance().Load("Metrics.lua");
	m_MaxDeltaTime = m_TimePerFrame * 2;
}

GameFlowManager::~GameFlowManager()
{
//	Metrics::Instance().Save("Metrics.lua");
}

void		GameFlowManager::DPadNav(int	DirX, int DirY, int	Triggered)
{
	if (m_pCurrentState)
	{
		if (!m_Transition.IsRunning())
		{
			m_pCurrentState->DPadNav(DirX, DirY, Triggered);
		}
	}
}

void	GameFlowManager::Tick(float dt)
{
	//float	dt = TIMER().Delta();
	//float	time = TIMER().Time();
	//bool	IsPaused = !IsActive();
	//bool	Hickup = false;
     
    if(m_IsPause)
        return;

	if (m_pCurrentState)
	{
		
			if (!m_Transition.IsRunning())
			{
				PROFILE_BLOCK("GameLoop");
				m_pCurrentState->Tick(dt);
				if (m_pCustomTickCB) m_pCustomTickCB(dt);
				GET_KEYBOARD().Tick(dt);
				GET_MOUSE().Tick(dt);
			} else
			{
				PROFILE_BLOCK("GameLoop");
				m_pCurrentState->Tick(0);
				m_Transition.Tick(dt);
				if (m_pCustomTickCB) m_pCustomTickCB(dt);
				m_pCurrentState->TransitionTick(dt);
			}
					

		if (g_pApp)
		{
/*			if (g_pApp->IsInactive())
			{
				// do  nothing here
				int j = 0;
			} else*/
			{
					// is there a platform independent wait-state?
//#ifdef _PLATFORM_WIN32
//			Sleep(1);
//#endif// _PLATFORM_WIN32
//#ifdef _PLATFORM_SDL
//			SDL_Delay(1);
//#endif
				PROFILE_BLOCK("Render");
				Render();
				if (m_Transition.IsRunning())
				{
					m_Transition.Render();
				}
				
			}
		}
		for (unsigned int i=0; i<m_DelayedScripts.size(); i++)
		{
			DelayedScript*	Delay = &m_DelayedScripts[i];
			Delay->Delta-=dt;
			if (Delay->Delta < 0)
			{
				std::string	TempStr = Delay->Script;
				m_DelayedScripts.erase(m_DelayedScripts.begin() + i);
				// there's a chance the script itself will add a delayed script state... oh, the pain..
				console::Console::Instance().Do(TempStr);
				return;	// don't open ourselves up to crashes on this.. later will be fine
			}
		}

	} else
	{
		assert((m_pTransitionState || m_pCurrentState) && "No valid states! Game won't run!");
	}
	if (m_pTransitionState)
	{
		m_TransitionTime -= dt;
		if (m_TransitionTime < 0.0f)
		{
			JumpToState(m_pTransitionState);
		}
	}
}



void	GameFlowManager::Render()
{
	
	// call this from a seperate function?
	
	if (m_pCurrentState && m_pCurrentState->IsCurrent())
	{
		if (!m_pCurrentState->Render())
		{
		
			if (!g_pApp->IsInactive())
			{
				if (g_pApp->GetHandler()->IsFullscreen())
				{
					// ADRIAN: don't reset fullscreen app while not having focus
					if (!g_pApp->GetHandler()->HasFocus()) return;
				
					// ADRIAN: don't reset fullscreen app while alt is down
					#ifdef _PLATFORM_WIN32	// Allan: this is a win32 only bug!
					if ( (0x8000 & GetAsyncKeyState(VK_LMENU)) || (0x8000 & GetAsyncKeyState(VK_RMENU)) ) return;
					#endif // _PLATFORM_WIN32
				}

				g_pApp->ResetDevice();
			}

		}
	}
}

void	GameFlowManager::Shutdown()
{
	if (m_pCurrentState)
		m_pCurrentState->OnExit();
	m_pCurrentState = NULL;

    // erase all the elements
	ClearSet();
}

const std::string&			GameFlowManager::GetCurrentStateName() const
{
	static std::string None = "unknown";
	if (m_pCurrentState) return m_pCurrentState->GetName();
	return None;
}
const std::string&		GameFlowManager::GetLastStateName() const
{
	static std::string None = "unknown";
	if (m_pPreviousState) return m_pPreviousState->GetName();
	return None;
}


void	GameFlowManager::JumpToState(GameState* pNewState)
{
	assert(pNewState && "Trying to enter an invalid state into game flow manager");
	if (!pNewState)
	{
		_LOG(MSG_WARNING, "Trying to enter an invalid state into game flow manager");
		return;
	}

	// allow the previous state to clean up after itself
	if (m_pCurrentState)
		m_pCurrentState->OnExit();

	// track the previous state: sometimes usefull (returning from pause, etc)
	m_pPreviousState = m_pCurrentState;
	
	// assign new state
	m_pCurrentState = pNewState;

	// clear old jump state
	m_pTransitionState = NULL;

	// start up state
	if (m_pCurrentState)
		m_pCurrentState->OnEnter();

	// force the timer to ignore the jump of any assets being loaded
	TIMER().Tick();
	TIMER().Tick();
}

void	GameFlowManager::JumpToState(const std::string& Name)
{
	GameState* pNewState = GetElement(Name);
	if (pNewState)
		JumpToState(pNewState);
	else
		_LOG(MSG_WARNING, "Trying to jump to an invalid state");

}

void	GameFlowManager::TransitionToState(const std::string& Name, float Delay)
{
	GameState* pNewState = GetElement(Name);
	//assert(pNewState && "Trying to enter an invalid state into game flow manager");
	if (!pNewState)
	{
		_LOG(MSG_FATAL, "Unable to go to state " << Name);
		return;
	}

	m_pTransitionState	= pNewState;
	m_TransitionTime	= Delay;

//	if (m_pCurrentState)	m_pCurrentState->SetTransition(GameState::FADE_OUT);
//	if (pNewState)			m_pTransitionState->SetTransition(GameState::FADE_IN);
}

GameState*	GameFlowManager::AddTransitionState(const std::string& Name, GameState* pNewState)
{
	assert(pNewState && "Trying to enter an invalid state into game flow manager");

	if (ElementExists(Name))
	{
		// delete old one.
		_LOG(MSG_ERROR, "Duplicating the same game name into the game flow manager: " << Name);
		RemoveElement(Name);		
		
	}

	pNewState->SetName(Name);
	AddElement(Name, pNewState);
	return pNewState;
}

bool	GameFlowManager::IsActive()
{
	const float	LOCK_OUT	= 3.0f / 10.0f; // 1/3 second lockout after switching
	float	Time =TIMER().Time(); 
	bool	Res = Time-m_LastGainFocus > LOCK_OUT;

	if (g_pApp)
		Res = Res && !g_pApp->IsInactive();
	return Res;
}

void	GameFlowManager::AddDelayedScript(const std::string& Script, float Delay, const std::string& ID)
{
	DelayedScript	DS;
	DS.Delta = Delay;
	DS.Script = Script;
	DS.ID = ID;
	m_DelayedScripts.push_back(DS);
}

void	GameFlowManager::RemoveDelayedScript(const std::string& Script)
{
	for (int i = 0; i < (int)m_DelayedScripts.size(); i++)
	{
		DelayedScript& ds = m_DelayedScripts[i];
		if (0 == stricmp(Script.c_str(), ds.Script.c_str()))
		{
			m_DelayedScripts.erase(m_DelayedScripts.begin() + i);
			break;
		}
	}
}

void	GameFlowManager::ClearDelayedScript(const std::string& ID)
{
	if (ID.empty())
		m_DelayedScripts.clear();
	else if (!m_DelayedScripts.empty())
	{
		for (int i = (int)(m_DelayedScripts.size() - 1); i >= 0; i--)
		{
			DelayedScript& ds = m_DelayedScripts[i];
			if (!ds.Script.empty() && (0 == stricmp(ID.c_str(), ds.Script.c_str())))
				m_DelayedScripts.erase(m_DelayedScripts.begin() + i);
		}
	}
}

void	GameFlowManager::SetTransitionEffect(const std::string& Effect, const std::string& State, const std::string& Script, GameTransition::TransitionEvent	OnFinish)
{
	m_Transition.StartTransition(Effect, State, Script, OnFinish);
}


////////////////////////
// Scripthelpers


static 	void				DelayScript(const std::string& Name, float Delay)
{
	GameFlowManager::Instance().AddDelayedScript(Name, Delay);
}


//// quick functions to access the game flow manager
static 	void				GoState(const std::string& Name, float Delay)
{
	GameFlowManager::Instance().TransitionToState(Name, Delay);
}
static 	void				JumpState(const std::string& Name)
{
//GameFlowManager::Instance().JumpToState(Name);
	GameFlowManager::Instance().TransitionToState(Name, 0.0f);	// a pure jump from console will crash the listener
}

static 	void				AddScreen(const std::string& Name, const std::string& File, const std::string& OnEnter = "", const std::string& OnExit = "")
{
	gameflow::GameFlowManager&	Game = gameflow::GameFlowManager::Instance();
	Game.AddTransitionState(Name, new UIState(Game.GetCamera(), File, OnEnter, OnExit));
	_LOG(MSG_COMMENT, "Adding screen " << Name << " from file " << File);
}

const std::string&	GetStateName()
{
	return GameFlowManager::Instance().GetCurrentStateName();
}

// Can't have a static and a member function with the same name, so add Dir
// to 
const std::string&	GetLastStateNameDir()
{
	return GameFlowManager::Instance().GetLastStateName();
}

static void	SetTransitionEffectDir(const std::string& Name, const std::string& To, const std::string& Script)
{
	GameFlowManager::Instance().SetTransitionEffect(Name, To, Script);
}
void GameFlowManager::Register(LuaPlus::LuaState* _pScript)
{
#ifdef _ENABLE_LUABIND
	
	using namespace luabind;
	
    lua_State * L = _pScript->GetCState ( );
	luabind::module(L)
    [
    luabind::class_< GameFlowManager >( "GameFlowManager" )
	 .def( "IsActive",                 &GameFlowManager::IsActive )
    ];
	
	module(L)
    [	def(  "AddGameScreen",			&AddScreen) ,
		def(  "GoState",				&GoState  ),
		def(  "JumpState",				&JumpState),
		def(  "GetCurrentState",		&GetStateName 		 ),		// const std::string&	GetCurrentStateName() const;
		def(  "GetLastState",			&GetLastStateNameDir ),		// const std::string&	GetLastStateName() const;
		def(  "DelayScript",			&DelayScript),
		def(  "SetTransitionEffect",	&SetTransitionEffectDir)
	 ];
	
#endif // _ENABLE_LUABIND
	
}